package com.riekoff.particlecubeworld.player;

import java.util.List;

import cc.creativecomputing.CCApp;
import cc.creativecomputing.CCApplicationManager;
import cc.creativecomputing.control.CCControl;
import cc.creativecomputing.events.CCKeyEvent;
import cc.creativecomputing.graphics.CCColor;
import cc.creativecomputing.graphics.export.CCScreenCapture;
import cc.creativecomputing.graphics.shader.CCGLSLShader;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureFilter;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureTarget;
import cc.creativecomputing.graphics.texture.CCTexture.CCTextureWrap;
import cc.creativecomputing.graphics.texture.CCTexture2D;
import cc.creativecomputing.graphics.texture.CCTextureCubeMap;
import cc.creativecomputing.graphics.texture.CCTextureData;
import cc.creativecomputing.graphics.texture.CCTextureIO;
import cc.creativecomputing.io.CCIOUtil;
import cc.creativecomputing.math.CCMath;
import cc.creativecomputing.math.CCVector3f;
import cc.creativecomputing.math.CCVector3i;
import cc.creativecomputing.math.random.CCRandom;
import cc.creativecomputing.math.util.CCArcball;
import cc.creativecomputing.util.logging.CCLog;

import com.riekoff.particlecubeworld.editor.visual.ABoxVisualFinal;
import com.riekoff.particlecubeworld.player.APlayListController.ATileFigure;

import de.negativrekord.blockworld.KnnClassifier2;

public class ATileworldApp extends CCApp {
	
	
	private class ANoiseModulation{
		private CCTexture2D _myRandomTexture;
		
		@CCControl(name = "octaves", min = 1, max = 10)
		private int octaves = 4; 
		@CCControl(name = "gain", min = 0, max = 1)
		private float gain = 0.5f; 
		@CCControl(name = "lacunarity", min = 0, max = 10)
		private float lacunarity = 2f; 
		@CCControl(name = "scale", min = 0, max = 1)
		private float scale = 0.5f; 
		
		private ANoiseModulation(){

			CCRandom myRandom = new CCRandom();
			CCColor[][] myBaseColorMap = new CCColor[256][256];
			
			for (int y=0;y<256;y++){
				for (int x=0;x<256;x++){
					myBaseColorMap[x][y] = new CCColor(myRandom.random(),0,0,0);
				}
			}

			for (int y=0;y<256;y++){
				for (int x=0;x<256;x++){
					int x2 = (x + 37) % 256;
					int y2 = (y + 17) % 256;
					myBaseColorMap[x2][y2].g = myBaseColorMap[x][y].r;
				}
			}
			
			CCTextureData myData = new CCTextureData(256,256);
			for(int x = 0; x < myData.width(); x++){
				for(int y = 0; y < myData.height(); y++){
					myData.setPixel(x, y, myBaseColorMap[x][y]);
				}
			}
			
			_myRandomTexture = new CCTexture2D(myData);
			_myRandomTexture.textureFilter(CCTextureFilter.LINEAR);
			_myRandomTexture.wrap(CCTextureWrap.REPEAT);
		}
		
		private void apply(CCGLSLShader theShader){
			g.texture(5,_myRandomTexture);
			_myShader.uniform1i("randomTexture",5);
			_myShader.uniform2f("randomTextureResolution",_myRandomTexture.width(), _myRandomTexture.height());
			theShader.uniform1i("octaves", octaves);
			theShader.uniform1f("gain", gain);
			theShader.uniform1f("lacunarity", lacunarity);
			theShader.uniform1f("noiseScale", scale);
		}
	}
	
	private class ALightControls{
		@CCControl(name = "ambient", min = 0, max = 1)
		private float _cAmbient = 1f;
		@CCControl(name = "diffuse", min = 0, max = 1)
		private float _cDiffuse = 1f;
		@CCControl(name = "specular", min = 0, max = 1)
		private float _cSpecular = 1f;
		@CCControl(name = "shininess", min = 0, max = 100)
		private float _cShininess = 1f;
		@CCControl(name = "intensity", min = 0, max = 1)
		private float _cLightIntensity = 1f;
		@CCControl(name = "position x", min = -1000, max = 1000)
		private float _cPositionX = 1f;
		@CCControl(name = "position y", min = -1000, max = 1000)
		private float _cPositionY = 1f;
		@CCControl(name = "position z", min = -1000, max = 1000)
		private float _cPositionZ = 1f;
		@CCControl(name = "reflection blend", min = 0, max = 1)
		public float _cRefBlend = 0;
		@CCControl(name = "ref pow", min = 0, max = 10)
		public float _cRefPow = 0;
		@CCControl(name = "noise amount", min = 0, max = 1)
		public float _cNoise = 0;
		

		private CCTextureCubeMap _myCubeMap;
		
		private ALightControls(){
			_myCubeMap = new CCTextureCubeMap(CCTextureIO.loadCubeMapData(
				"cubemap/cube_posx.png",
				"cubemap/cube_negx.png",
				"cubemap/cube_posy.png",
				"cubemap/cube_negy.png",
				"cubemap/cube_posz.png",
				"cubemap/cube_negz.png"	
			));
		}
		
		private void apply(CCGLSLShader theShader){
			g.texture(4, _myCubeMap);

			theShader.uniform1i("cubeMap", 4);
			
			theShader.uniform4f ("lightPosition", _cPositionX, _cPositionY, _cPositionZ, 0f);
			theShader.uniform3f ("LightIntensity", _cLightIntensity, _cLightIntensity, _cLightIntensity) ;

			theShader.uniform3f ("Kd", _cDiffuse, _cDiffuse, _cDiffuse);            // Diffuse reflectivity
			theShader.uniform3f ("Ka", _cAmbient, _cAmbient, _cAmbient);;            // Ambient reflectivity
			theShader.uniform3f ("Ks", _cSpecular, _cSpecular, _cSpecular);            // Specular reflectivity
			theShader.uniform1f("Shininess", _cShininess);
			theShader.uniform3f ("uEyePosition", 0,0,10); 
			theShader.uniform1f ("refBlend", _cRefBlend); 
			theShader.uniform1f ("refPow", _cRefPow); 
			theShader.uniform1f ("noiseAmount", _cNoise); 
		}
	}
	
	private class ATileControls{
		@CCControl(name = "tile raster size", min = 0.001f, max = 1f)
		private float _cTileRasterWidth = 0.1f;
		
		@CCControl(name = "motar thickness", min = 0.0001f, max = 1f)
		private float _cMotarThickness = 0.01f;
		
		@CCControl(name = "motar smooth", min = 0.0001f, max = 1f)
		private float _cMotarSmooth = 0.01f;
		
		@CCControl(name = "motar edge smooth", min = 0.0001f, max = 0.01f)
		private float _cMotarEdgeSmooth = 0.01f;
		
		@CCControl(name = "debug")
		public boolean _cDebug = false;
		
		@CCControl(name = "randomrefresh", min = 0, max = 10)
		public float _cRandomRefresh = 0;
		
		private CCTexture2D _myRandomTexture;
		private CCTextureData myRandom;
		

		
		private ATileControls(CCApp theApp){
			
			myRandom = new CCTextureData(200,200);
			for(int x = 0; x < myRandom.width(); x++ ){
				for(int y = 0; y < myRandom.height(); y++ ){
					myRandom.setPixel(x, y, new CCColor(CCMath.random(),CCMath.random(),CCMath.random()));
				}
			}
			_myRandomTexture = new CCTexture2D(myRandom, CCTextureTarget.TEXTURE_RECT);
			_myRandomTexture.wrap(CCTextureWrap.REPEAT);
		}
		
		public void apply(CCGLSLShader theShader){
			float myProb = CCMath.pow(10, _cRandomRefresh);
			for(int x = 0; x < myRandom.width(); x++ ){
				for(int y = 0; y < myRandom.height(); y++ ){
					if(CCMath.random() > 1 / myProb)continue;
					myRandom.setPixel(x, y, new CCColor(CCMath.random(),CCMath.random(),CCMath.random()));
				}
			}
			_myRandomTexture.updateData(myRandom);
			
			g.texture(0, _myPlayList.patterns());
			g.texture(1, _myPlayList.zones());
			g.texture(2, _myPlayList.colors());
			g.texture(3, _myRandomTexture);
			
			float tileMotarWidth = 1;
			float motarStep = _cMotarThickness;
			
			theShader.uniform1f("tileMotarWidth", tileMotarWidth);
			theShader.uniform1f("motarStep", motarStep);
			theShader.uniform1f("motarSmooth", _cMotarSmooth);
			theShader.uniform1f("motarEdgeSmooth", _cMotarEdgeSmooth);
			
			theShader.uniform1f("patternScale", _cTileRasterWidth);
			theShader.uniform1i("debug", _cDebug ? 1 : 0);
			
			theShader.uniform1i("random", 3);
			theShader.uniform1f("time", _myTime * 30);
			
			theShader.uniform1i("patterns", 0);
			theShader.uniform2fv("patternDimensions", _myPlayList.patternDimensions());
			
			theShader.uniform1i("zones", 1);
			theShader.uniform3fv("zoneDimensions", _myPlayList.zoneInfos());
			theShader.uniform1i("colorMaps", 2);
		}
	}
	
	private class AClassifiyControl implements AGlobalTimingProgressListener{
	
		private class BlendPreset {
			public BlendPreset (float r0, float r1, float f0, float f1) {
				this.r0 = r0;
				this.r1 = r1;
				this.f0 = f0;
				this.f1 = f1;
			}
			float r0 = 0f;
			float r1 = 0f;
			float f0 = 0f;
			float f1 = 0f;
		}
		
		
		int durations[] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1};
		
		@CCControl(name="blend preset", min=0, max=2)
		int _cBlendPreset = 0;
		
		BlendPreset[] blendPresets = {
			new BlendPreset(0, 0f, 0f, 1f),
			new BlendPreset(400f, 0f, 0.05f, 1f),
			new BlendPreset(833f, 1000f, 0.35f, 0.1f),
			new BlendPreset(500f, 0, 1f, 0.1f)
		};
		
		@CCControl(name="pattern random", min=0,max=1000)
		float patternRandom = 0;
		
		@CCControl(name="blend random 0", min=0,max=1000)
		float blendRandom0 = 0;
		@CCControl(name="blend random 1", min=0,max=1000)
		float blendRandom1 = 0;
		
		@CCControl(name="random frequency 0", min=0.01f, max=1f)
		float _cRandomScale0 = 1f;
		
		@CCControl(name="random frequency 1", min=0.01f, max=1f)
		float _cRandomScale1 = 1f;
		
		@CCControl(name="classifier angle 0", min=0, max=CCMath.TWO_PI)
		float _cClassifierAngle0 = 0;
		
		@CCControl(name="classifier angle 1", min=0, max=CCMath.TWO_PI)
		float _cClassifierAngle1 = 0;
		
		
		private AClassifiyControl(){
		}
		
		private void apply(CCGLSLShader theShader){

			theShader.uniform1i("classStart", CCMath.max(0, _myClassifier2.centerID() - 2));
			theShader.uniform1i("classEnd", CCMath.min(20, _myClassifier2.centerID() + 2));
			CCVector3f[] myCenters = new CCVector3f[20];
			int[] myIndices = _myClassifier2.classIndices();
			
			CCVector3i[] myFigureInfos = new CCVector3i[20];
			float[] myMotars = new float[20];
			float[] myZoneAdds = new float[20];
			float[] myZoneMods = new float[20];
			float[] myZoneScales = new float[20];
			float[] myFrameRates = new float[20];
			
			List<ATileFigure> myPlayList = _myPlayList.currentList();
			_myGlobalTimingController.reset(_myPlayList.getDurations());
			_myClassifier2.recalc(myPlayList.size(), 4000, _myPlayList.getDurations().length/2, _cClassifierAngle0, _cClassifierAngle1);
			
			for(int i = 0; i <  myPlayList.size() && i < myFigureInfos.length;i++){
				ATileFigure myFigure = myPlayList.get(i);
				myFigureInfos[i] = new CCVector3i(
					myFigure.patternIndex(),
					myFigure.zoneIndex(),
					myFigure.colorIndex()
				);
				myMotars[i] = myFigure.motarBrightness();
				myZoneAdds[i] = myFigure.zoneAdd();
				myZoneMods[i] = myFigure.zoneMod();
				myZoneScales[i] = myFigure.zoneScale();
				myFrameRates[i] = myFigure.frameRate();
			}
			
			for(int i = 0; i < 20; i++){
				myCenters[i] = _myClassifier2.center(i);
			}
			theShader.uniform3fv("classCenters", myCenters);
			
			theShader.uniform1iv("figureIndices", myIndices);
			theShader.uniform3iv("figureInfos", myFigureInfos);
			theShader.uniform1fv("motars", myMotars);
			theShader.uniform1fv("zoneAdds", myZoneAdds);
			theShader.uniform1fv("zoneMods", myZoneMods);
			theShader.uniform1fv("zoneCoordScales", myZoneScales);
			theShader.uniform1fv("frameRates", myFrameRates);
			
			theShader.uniform1f("patternBlendRandom", patternRandom);
			theShader.uniform1f("zoneBlendRandom", 0.0f);
			theShader.uniform1f("colorBlendRandom0", blendPresets[_myPlayList.blendPreset(_myClassifier2.centerID())].r0);
			theShader.uniform1f("colorBlendRandom1", blendPresets[_myPlayList.blendPreset(_myClassifier2.centerID())].r1); //CCMath.blend(blendRandomMin, blendRandomMax, _myProgress));
			theShader.uniform1f("randomScale0", blendPresets[_myPlayList.blendPreset(_myClassifier2.centerID())].f0);
			theShader.uniform1f("randomScale1", blendPresets[_myPlayList.blendPreset(_myClassifier2.centerID())].f1);
		}
		
		private float _myProgress = 0.0f;

		@Override
		public void progress(float theProgress) {
			_myProgress = theProgress;
		}

		@Override
		public void update(float theDeltaTime) {}

		@Override
		public void onRestart() {}
	}
	
	@CCControl(name = "debug")
	private boolean _cDebug = false;
	
	private CCGLSLShader _myShader;
	
	private ABoxVisualFinal _myBlockworldVisualFinal;
	private ACameraSetController _myCameraSetController;
	private AGlobalTimingController _myGlobalTimingController;

	@CCControl(name="center movement", tabName="classify")
	private KnnClassifier2 _myClassifier2 = new KnnClassifier2(40,1000);
	
	private ATileControls _myTileControls;
	private ALightControls _myLightControls;
	private ANoiseModulation _myNoiseModulation;
	private AClassifiyControl _myClassifyControls;
	private APlayListController _myPlayList;
	private CCArcball _myArcball;

	@Override
	public void setup() {
		
		_myPlayList = new APlayListController(this);
		_myBlockworldVisualFinal = new ABoxVisualFinal(this);
		
		_myGlobalTimingController = new AGlobalTimingController (KnnClassifier2.sum(_myPlayList.getDurations()));
		_myGlobalTimingController.events().add(_myClassifier2);
		addControls("choreo", "global", 0, _myGlobalTimingController);
		
		_myCameraSetController = new ACameraSetController(_myBlockworldVisualFinal.cameraSet());
		_myGlobalTimingController.events().add(_myCameraSetController);
		addControls("cameras", "camera", 0, _myCameraSetController);
		
		_myGlobalTimingController.events().add(_myPlayList);
		addControls("choreo", "playlist", 0, _myPlayList);

		
		
		_myTileControls = new ATileControls(this);
		addControls("app", "tile", 0, _myTileControls);

		_myLightControls = new ALightControls();
		addControls("app", "light", 1, _myLightControls);
		
		_myNoiseModulation = new ANoiseModulation();
		addControls("app", "noise", 2, _myNoiseModulation);
		
		_myClassifyControls = new AClassifiyControl();
		addControls("app", "classify", 3, _myClassifyControls);
		_myGlobalTimingController.events().add(_myClassifyControls);

		addControls("visual", "visual", 1, _myBlockworldVisualFinal);
		addControls("app", "app", this);
		
		_myShader = new CCGLSLShader(
			CCIOUtil.classPath(this, "tex_brick_vertex.glsl"),
			CCIOUtil.classPath(this, "tex_brick_fragment.glsl")
		);
		_myShader.load();
		
		_myArcball = new CCArcball(this);
		_myBlockworldVisualFinal.recalc();
	}
	
	private float _myTime = 0;
	
	private boolean _mySetCursour = true;

	@Override
	public void update(final float theDeltaTime) {
		_myTime += theDeltaTime;
		_myShader.checkUpdates();
		_myGlobalTimingController.update(theDeltaTime);
		_myBlockworldVisualFinal.update(theDeltaTime);
		
		if(areControlsVisible() && !_mySetCursour){
			_mySetCursour = true;
			cursor();
		}
		if(!areControlsVisible() && _mySetCursour){
			_mySetCursour = false;
			noCursor();
		}
	}
	
	@Override
	public void keyPressed(CCKeyEvent theKeyEvent) {
		switch(theKeyEvent.keyCode()){
		case VK_Z:
			break;
		case VK_S:
			CCScreenCapture.capture("export/" + frameCount + ".png", width, height);
			break;
		case VK_1:
			_myPlayList.loadFromCSV(this, "playlists_csv");
			break;
		case VK_2:
			_myPlayList.loadFromCSV(this, "playlists_calm_csv");
			break;
		}
	}

	@Override
	public void draw() {
		g.clear();
		
		g.color(255);
		_myShader.start();
		
		_myTileControls.apply(_myShader);
		_myLightControls.apply(_myShader);
		_myNoiseModulation.apply(_myShader);
		_myClassifyControls.apply(_myShader);
		
		if(_cDebug){
			g.pushMatrix();
			_myArcball.draw(g);
			_myBlockworldVisualFinal.render(g);
			g.popMatrix();
		}else{
			_myBlockworldVisualFinal.draw(g);
		}
		
		
		g.noTexture();
		_myShader.end();
		
		if(_cDebug){
			g.pushMatrix();
			_myArcball.draw(g);
			_myBlockworldVisualFinal.cameraSet().drawDebug();
			_myClassifier2.draw(g);
			g.popMatrix();
		}
		
		g.color(255);
		g.viewport(0, 0, width, height);
		g.clearDepthBuffer();
	}

	public static void main(String[] args) {
		CCApplicationManager myManager = new CCApplicationManager(ATileworldApp.class);
		myManager.settings().size(1920 * 3, 1080);
		myManager.settings().undecorated(true);
		myManager.settings().location(0, 0);
		myManager.settings().display(0);
		myManager.settings().vsync(false);
		myManager.settings().alwaysOnTop(false);
		myManager.settings(args);
		myManager.start();
	}
}
